/**
##############################################################################

DOCOMO Innovations, Inc.

Copyright © 2011 DOCOMO Innovations, Inc. All rights reserved.

 

Date of Preparation: 08/01/2011

Contact: kozat@docomoinnovations.com

 

All intellectual property rights related to this software are vested in DOCOMO Innovations.

DOCOMO Innovations is under no obligation to maintain or support the software.

Neither DOCOMO Innovations nor its employees are responsible for any damage or loss resulting from the use of the software.

##############################################################################
 */

package umich.infinity.facebookconn;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TIOStreamTransport;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import umich.infinity.proxy.Para;
import umich.infinity.proxy.SharedData;
import umich.infinity.proxy.Util;
import umich.infinity.thrift.Attr;
import umich.infinity.thrift.Constants;
import umich.infinity.thrift.DirEntry;
import umich.infinity.thrift.FriendAlbum;
import umich.infinity.thrift.FriendEntry;
import umich.infinity.thrift.PhotoEntry;

public class FacebookUpdateMonitor implements Runnable {
	//String[] friendList = {"me", "2232434", "531338961","653857887","100002394671425"};
	//String[] friendList = {"531338961","100002394671425"};

	//FacebookConn facebook;
	//public String uid;
	
	public String NewTime = "9999-01-01T00:00:00+0000";
	SharedData sharedData;
	
	ThreadPoolExecutor queryFriendThreadPool;
	ThreadPoolExecutor queryAlbumThreadPool;
	ThreadPoolExecutor queryUserThreadPool;
	
	public Semaphore semaphore;
	
	public FacebookUpdateMonitor(SharedData sharedData) {
		this.sharedData = sharedData;
		this.semaphore = new Semaphore(1);
		
		System.out.println(Util.getCurTimeString());
		
		queryFriendThreadPool = new ThreadPoolExecutor(Para.QueryFriendThreadPoolSize, 
			Para.QueryFriendThreadPoolMaxSize, Para.QueryFriendThreadPoolKeepAliveTime,
			Para.ThreadPoolTimeUnit, new LinkedBlockingQueue<Runnable>());
		
		queryAlbumThreadPool = new ThreadPoolExecutor(Para.QueryAlbumThreadPoolSize, 
				Para.QueryAlbumThreadPoolMaxSize, Para.QueryAlbumThreadPoolKeepAliveTime,
				Para.ThreadPoolTimeUnit, new LinkedBlockingQueue<Runnable>());
		
		queryUserThreadPool = new ThreadPoolExecutor(Para.QueryUserThreadPoolSize, 
				Para.QueryUserThreadPoolMaxSize, Para.QueryUserThreadPoolKeepAliveTime,
				Para.ThreadPoolTimeUnit, new LinkedBlockingQueue<Runnable>());
		
		//this.facebook = facebook;
		//this.uid = uid;
		/*

		String fn2 = Util.friendAlbumName(friendList[0]);

		File f2 = new File(fn2);
		FriendAlbum res = null;
		FriendAlbum fa;
		try {
			fa = readFriendAlbumFromFile(f2);		
		try {
			System.out.println(FriendAlbumToJson(fa).toString(2));
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (TException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}*/
		/*
		try {
				FileInputStream fis = new FileInputStream(f2);
				BufferedInputStream bis = new BufferedInputStream (fis);
				String jsonS = FacebookUtil.readString(bis);
				res = FriendAlbumFromJson(FacebookUtil.parseJson(jsonS));	

		FileOutputStream fos2 = new FileOutputStream(fn2+".thrift");
		TIOStreamTransport tt = new TIOStreamTransport(fos2);
		TBinaryProtocol tpl = new TBinaryProtocol(tt);

		res.write(tpl);

		tt.close();

		FileInputStream fis2 = new FileInputStream(fn2+".thrift");
		tt = new TIOStreamTransport(fis2);
		tpl = new TBinaryProtocol(tt);

		FriendAlbum fa = new FriendAlbum();
		fa.read(tpl);

		tt.close();

		fos2 = new FileOutputStream(fn2+".thrift2");
		tt = new TIOStreamTransport(fos2);
		tpl = new TBinaryProtocol(tt);

		PhotoEntry pe = new PhotoEntry();
		pe.id = "id12345";
		pe.name = "name";
		pe.source = "http://source link";

		fa.photos.add(pe);
		fa.updatedTime = "abc";

		fa.write(tpl);

		tt.close();

		System.out.println(FriendAlbumToJson(fa).toString(2));
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FacebookError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		 */
		/*
		JSONObject json = new JSONObject();

		try {
			json.put("c", "a");

		json.put("b", "b");
		json.put("a", "d");


		System.out.println(json.toString(2));
		} catch (JSONException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		 */
	}
	
	public class FriendInfo implements Comparable<FriendInfo>{
		String fid;
		String name;
		
		public int compareTo(FriendInfo o) {
			return this.name.compareTo(o.name);
		}
	}

	public class MyAlbum implements Comparable<MyAlbum> {
		String id;
		String updated_time;
		String name;
		int type = Constants.PhotoEntryTypePhoto;; 
		int count;
		
		@Override
		public int compareTo(MyAlbum another) {
			return another.updated_time.compareTo(this.updated_time);
		}
	}
	
	//A helper class to be used as parameters passed to function calls, when time is updated,
	//it is also reflected in the caller side
	public class MyTime {
		public String time;
		
		public MyTime(String time) {
			this.time = time;
		}
		
		public MyTime() {
			this.time = Para.DefaultStartTime;
		}		
	}
	
	class QueryAlbumTask implements Runnable {
		FacebookUser fbUser;
		int i;
		MyAlbum mAlbum;
		List<PhotoEntry> newPhotoList;
		MyTime largetTime;
		List<DirEntry> fnList;

		public QueryAlbumTask(FacebookUser fbUser, int i, MyAlbum mAlbum, List<PhotoEntry> newPhotoList, List<DirEntry> fnList, MyTime largetTime) {
			this.fbUser = fbUser;
			this.mAlbum = mAlbum;
			this.newPhotoList = newPhotoList;
			this.largetTime = largetTime;
			this.i = i;
			this.fnList = fnList;
		}

		@Override
		public void run() {
			String albumId = mAlbum.id;
			try {
				/**********request***********/
				String photoRes = mAlbum.type==Constants.PhotoEntryTypePhoto ? fbUser.facebook.request(albumId+"/photos") : fbUser.facebook.request(albumId+"/videos") ;
				
				JSONObject photoJson = FacebookUtil.parseJson(photoRes);
				JSONArray photos = photoJson.getJSONArray("data");

				System.out.println("          Query AlbumID: " + albumId);
				for (int jj=0; jj<photos.length(); jj++) {

					JSONObject photo;
					try {
						photo = photos.getJSONObject(jj);

						String pid = photo.getString("id");
						String source = photo.getString("source");
						String pic = photo.getString("picture");

						System.out.println("  Friend " + i + " Photos " + jj + " " + pid);

						PhotoEntry pe = new PhotoEntry();
						pe.updatedTime = photo.getString("updated_time");

						if (photo.has("name")) {
							pe.name = photo.getString("name");
						} else {
							pe.name = "";
						}
						if (photo.has("comments")) {
							JSONArray comments = photo.getJSONObject("comments").getJSONArray("data");
							for (int tc = 0;tc<comments.length(); tc++) {
								JSONObject tag = comments.getJSONObject(tc);
								String ct = tag.getString("created_time");
								//System.out.println("comments " + tc + " " + ct);
								if (ct.compareTo(pe.updatedTime) > 0) {
									pe.updatedTime = ct;
								}
							}
							pe.comments = comments.length();
						} else {
							pe.comments = 0;
						}

						if (photo.has("tags")) {
							JSONArray tags = photo.getJSONObject("tags").getJSONArray("data");
							for (int tc = 0;tc<tags.length(); tc++) {
								JSONObject tag = tags.getJSONObject(tc);
								String ct = tag.getString("created_time");
								if (ct.compareTo(pe.updatedTime) > 0) {
									pe.updatedTime = ct;
								}
							}
							pe.tags = tags.length();
						} else {
							pe.tags = 0;
						}

						//Get the likes
						/**********request***********/
						//String likesRes = fbUser.facebook.request(pid+"/likes");
						//JSONObject likesJson = FacebookUtil.parseJson(likesRes);
						//JSONArray likes = likesJson.getJSONArray("data");

						pe.likes = 0;//likes.length();

						pe.album_id = albumId;
						if (photo.has("link")) {
							pe.links = photo.getString("link");
						} else {
							pe.links = "";
						}
						pe.id = pid;
						pe.source = source;
						pe.picture = pic;
						pe.type = (short) mAlbum.type;

						//System.out.println("P " + pe.id + " " + pe.name + " comments: " + pe.comments + " tags: " + pe.tags + " likes: " + pe.likes + "\n" + pe.links);

						if (pe.updatedTime.compareTo(fbUser.updatedTime[i]) > 0) {
							String sourceFn = (mAlbum.type==Constants.PhotoEntryTypePhoto) ? Util.photoName(pe.id) : Util.videoName(pe.id);
							cachePhoto(source, sourceFn);
							pe.photoSize = new File(sourceFn).length();
							String thumbFn = Util.photoThumbName(pe.id);
							cachePhoto(pic, thumbFn);
							pe.thumbSize = new File(thumbFn).length();
							
							//synchronized (fnList) {
								//fnList.add(dirEntryFromFileName(sourceFn, pe.updatedTime, Constants.PriorityNormal));
								//fnList.add(dirEntryFromFileName(thumbFn, pe.updatedTime, Constants.PriorityThumb));
							//}
							synchronized (largetTime) {
								if (pe.updatedTime.compareTo(largetTime.time) > 0) {
									//System.out.println("***Largest time updated from " + largetTime + " to " + pe.updatedTime);
									largetTime.time = pe.updatedTime;												
								}
							}
							
							synchronized (newPhotoList) {
								newPhotoList.add(pe);
							}
						}
					} catch (JSONException e) {
						e.printStackTrace();
					/*} catch (MalformedURLException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} catch (FacebookError e) {
						e.printStackTrace();*/
					}
				}
			} catch (JSONException e) {
				e.printStackTrace();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (FacebookError e) {
				e.printStackTrace();
			}
		}
	}
	
	class QueryFriendTask implements Runnable {
		FacebookUser fbUser;
		String fid;
		int i ;
		List<DirEntry> fnList;
		boolean newFriend;

		public QueryFriendTask(FacebookUser fbUser, String fid, int i, List<DirEntry> fnList, boolean newFriend) {
			this.fbUser = fbUser;
			this.fid = fid;
			this.i = i;
			this.fnList = fnList;
			this.newFriend = newFriend;
		}

		@Override
		public void run() {
			String albumRes;
			//System.out.println("QueryFriendTask for " + fbUser.uid + "_" + fid + " newUser " + newFriend + " Start... " + queryFriendThreadPool.getPoolSize() + " " + queryFriendThreadPool.getCompletedTaskCount() + "/" + queryFriendThreadPool.getTaskCount());
			
			if (newFriend) {
				getFriendInfo(fid);				
				return;
			}
			
			try {
				/**********request***********/
				albumRes = fbUser.facebook.request(fid+"/albums");

				JSONObject albumJson = FacebookUtil.parseJson(albumRes);
				JSONArray albums = albumJson.getJSONArray("data");

				//The list of albums that have been updated
				ArrayList<MyAlbum> albumList = new ArrayList<MyAlbum> ();

				//Check the tagged photo of the user, which is under /me/photos
				MyAlbum mAlbum1 = new MyAlbum();
				mAlbum1.type = Constants.PhotoEntryTypePhoto;
				
				MyAlbum mAlbum2 = new MyAlbum();
				mAlbum2.type = Constants.PhotoEntryTypeVideo;
				
				if (i == 0){
					mAlbum1.id = fbUser.uid;
					mAlbum2.id = fbUser.uid;					
				} else {
					mAlbum1.id = fid;
					mAlbum2.id = fid;					
				}
				
				albumList.add(mAlbum1);
				albumList.add(mAlbum2);

				MyTime largetTime = new MyTime();
				synchronized (fbUser.updatedTime[i]) {
					largetTime.time = fbUser.updatedTime[i];
				}

				for (int j=0;j<albums.length() && j < 1;j++) {
					MyAlbum mAlbum = new MyAlbum();
					mAlbum.type = Constants.PhotoEntryTypePhoto;

					JSONObject album;
					try {
						album = albums.getJSONObject(j);

						mAlbum.id = album.getString("id");
						if (album.has("updated_time")) {
							mAlbum.updated_time = album.getString("updated_time");
						} else {
							mAlbum.updated_time = album.getString("created_time");
						}
						mAlbum.name = album.getString("name");
						
						//Check whether the latest update of the album is cached
						
						if (mAlbum.updated_time.compareTo(fbUser.updatedTime[i]) > 0) {
							albumList.add(mAlbum);
						}
						if (mAlbum.updated_time.compareTo(largetTime.time) > 0) {
							//System.out.println("***Largest time updated from " + largetTime + " to " + mAlbum.updated_time);
							largetTime.time = mAlbum.updated_time;
						}
					} catch (JSONException e) {
						e.printStackTrace();
					}
				}

				//System.out.println("AlbumListSize " + albumList.size());
				if (albumList.size() > 0) {
					//Some albums have been updated
					List<PhotoEntry> newPhotoList = new ArrayList<PhotoEntry>();
					List<Future<?>> futures = new LinkedList<Future<?>>();

					for (int j=0;j<albumList.size();j++) {   //////test
						//System.out.println("Album " + j + " " + albumList.get(j).id);
						MyAlbum mAlbum = albumList.get(j);
						if (i < 2) { ///////test 
							QueryAlbumTask qat = new QueryAlbumTask (fbUser, i, mAlbum, newPhotoList, fnList, largetTime);

							futures.add(queryAlbumThreadPool.submit(qat));
						}
					}

					//Wait for all the children threads to complete
					for (Future<?> future:futures) {
						try {
							future.get();
						} catch (InterruptedException e) {
							e.printStackTrace();
						} catch (ExecutionException e) {
							e.printStackTrace();
						}
					}

					//By this time since all the children threads complete, largestTime will not be touched again
					String tmp = null;
					synchronized (fbUser.updatedTime[i]) {
						tmp = fbUser.updatedTime[i] = largetTime.time;
					}
					
					//boolean updated = updateFriendAlbum(fbUser.uid, fid, newPhotoList,tmp,albumList);
					
					//if (updated) {
					//	String tmpFAFn =  Util.friendAlbumName(fbUser.uid, fbUser.friends[i]);
					//	synchronized (fnList) {
					//		fnList.add(dirEntryFromFileName(tmpFAFn, fbUser.updatedTime[i], Constants.PriorityMeta));
					//	}
					//}
				}
			} catch (MalformedURLException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			} catch (FacebookError e) {
				e.printStackTrace();
			} catch (JSONException e) {
				e.printStackTrace();
			}
		}
		
		/**
		 * Get the basic information of the friend, including the profile photo
		 * @param fid friend id
		 * @return
		 */
		public FriendAlbum getFriendInfo(String fid) {
			String response;
			
			System.out.println("getFriendInfo " + fid);
			
			try {
				/**********request***********/
				response = fbUser.facebook.request(fid);

				JSONObject json = FacebookUtil.parseJson(response);
				FriendAlbum fa = new FriendAlbum();
				fa.friend = new FriendEntry();
				fa.friend.id = json.getString("id");
				fa.friend.name = json.getString("name");
				
				System.out.println("getFriendInfo " + fa.friend.id + " " + fa.friend.name);
				
				fa.friend.profilePicId = new PhotoEntry();
				fa.friend.profilePicId.id = null;
				fa.updatedTime = Para.DefaultStartTime;
				fa.photos = new ArrayList<PhotoEntry> ();

				//Get the profile picture
				/**********request***********/
				String albumRes = fbUser.facebook.request(fid+"/albums");
				JSONObject albumJson = FacebookUtil.parseJson(albumRes);
				JSONArray albums = albumJson.getJSONArray("data");

				ArrayList<MyAlbum> albumList = new ArrayList<MyAlbum> (albums.length());
				for (int j=0; j<albums.length();j++) {
					JSONObject album = albums.getJSONObject(j);
					if (album.getString("type").compareTo("profile") == 0) {
						/**********request***********/
						String photoRes = fbUser.facebook.request(album.getString("id")+"/photos");
						JSONObject photoJson = FacebookUtil.parseJson(photoRes);
						JSONArray photos = photoJson.getJSONArray("data");
						if (photos.length()>0) {
							JSONObject profile = photos.getJSONObject(0);							
							fa.friend.profilePicId.id = profile.getString("id");							
							String updatedTime = profile.getString("updated_time");
							fa.friend.profilePicId.updatedTime = updatedTime;
							String fn = Util.photoThumbName(fa.friend.profilePicId.id);
							cachePhoto(profile.getString("picture"), fn);
							File f = new File(fn);
							fa.friend.profilePicId.thumbSize = f.length();
							fa.friend.profilePicId.photoSize = Constants.PhotoUnavailable;
							//ArrayList <DirEntry> l = new ArrayList<DirEntry> (1);
							//l.add(dirEntryFromFileName(fn, updatedTime, Para.PRIORITY_THUMB));
							//updateNoti(fbUser.uid, l);									
						}
					}
				}
				
				String fn = Util.friendAlbumName(fbUser.uid,fid);
				File f = new File (fn);
				writeFriendAlbumToFile(fa, f);
				
				return fa;
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (FacebookError e) {	
				e.printStackTrace();
			} catch (JSONException e) {
				e.printStackTrace();
			} catch (TException e) {
				e.printStackTrace();
			}

			return null;
		}
	}
	
	class QueryUserTask implements Runnable {
		FacebookUser fbUser;
		public QueryUserTask(FacebookUser fbUser) {
			this.fbUser = fbUser;
		}
		@Override
		public void run() {
			if (fbUser.newUser) {
				initUser();
				fbUser.newUser = false;
			}

			//The list of files, including album files and photos files of all friends,
			//that have been updated.
			List<DirEntry> fnList = new ArrayList<DirEntry> ();

			List<Future<?>> futures = new LinkedList<Future<?>>();

			for (int i=0;i<fbUser.friends.length;i++) {
				String fid = fbUser.friends[i];					
				QueryFriendTask qat = new QueryFriendTask(fbUser, fid, i, fnList, false);

				futures.add(queryFriendThreadPool.submit(qat));						
			}

			//Wait for all the children threads to complete
			for (Future<?> future:futures) {
				try {
					future.get();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (ExecutionException e) {
					e.printStackTrace();
				}
			}
			
			if (fnList.size() > 0) {
				updateNoti(fbUser.uid, fnList);
				fnList.clear();
			}
			System.out.println("User end.");
		}	
			
		/**
		 * Add a new user to the monitoring list. 
		 * Initiate friends by reading the friend list from the server. Initiate updatedTime, reading from the local cached files if available.
		 */
		public void initUser() {
			try {
				String friendRes;
				/**********request***********/
				friendRes = fbUser.facebook.request("me/friends");

				JSONObject friendJson = FacebookUtil.parseJson(friendRes);
				JSONArray friends = friendJson.getJSONArray("data");

				int ll = friends.length();
				
				System.out.println("Length " + (1+ll));
				
				FriendInfo[] fi = new FriendInfo[1+ll];
				
				fi[0] = new FriendInfo();
				fi[0].fid = fbUser.uid;
				fi[0].name = "";

				for (int jj=0; jj<ll; jj++) {
					JSONObject photo = friends.getJSONObject(jj);
					String fid = photo.getString("id");
					String name = photo.getString("name");
					
					fi[jj+1] = new FriendInfo();
					fi[jj+1].fid = fid;
					fi[jj+1].name = name;
				}
				
				//Arrays.sort(fi);
				
				fbUser.friends = new String[fi.length];
				fbUser.updatedTime = new String[fbUser.friends.length];
				
				for (int jj=0; jj<fbUser.friends.length; jj++) {
					fbUser.friends[jj] = fi[jj].fid;				

					System.out.println("Friend " + jj + " " + fbUser.friends[jj] + " " + fi[jj].name);
				}
			} catch (MalformedURLException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			} catch (FacebookError e) {
				e.printStackTrace();
			} catch (JSONException e) {
				e.printStackTrace();
			}
			
			if (fbUser.friends == null) {
				fbUser.friends = new String[0];			
			}

			List<Future<?>> futures = new LinkedList<Future<?>>();
			
			for (int i=0;i<fbUser.friends.length;i++) {
				String fid = fbUser.friends[i];
				String fn = Util.friendAlbumName(fbUser.uid,fid);
				File f = new File (fn);

				if (i==0) {
					fbUser.updatedTime[i] = "";
				} else {
					fbUser.updatedTime[i] = Para.DefaultStartTime;
				}
				
				try {	
					//System.out.println(f_album[i].getName());
					if (f.exists()) {
						FriendAlbum fa = readFriendAlbumFromFile(f);
						fbUser.updatedTime[i] = fa.updatedTime;
						System.out.println("File exists");						
					} else {
						QueryFriendTask qat = new QueryFriendTask(fbUser, fid, i, null, true);
						futures.add(queryFriendThreadPool.submit(qat));
					}
					
					System.out.println("updatedTime[" + i + "]: " + fbUser.updatedTime[i]);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (TException e) {
					e.printStackTrace();
				}
			}
			
			//Wait for all the children threads to complete
			for (Future<?> future:futures) {
				try {
					future.get();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (ExecutionException e) {
					e.printStackTrace();
				}
			}
		
			//Write the friend list to file
			String fn = Util.friendListName(fbUser.uid);
			File f = new File(fn);
			if (f != null) {
				System.out.println(f);
			}
			FileOutputStream fos;
			try {
				fos = new FileOutputStream(f);
				ObjectOutputStream oos = new ObjectOutputStream (fos);
				oos.writeObject(fbUser.friends);
				oos.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("Write the friend list to file End.");
			String curTime = Util.getCurTimeString();
			
			//Add notification for the client
			
			ArrayList<DirEntry> l = new ArrayList<DirEntry> ();
			l.add(dirEntryFromFileName(fn, curTime, Constants.PriorityMeta));
			
			for (int i=0;i<fbUser.friends.length;i++) {
				String fid = fbUser.friends[i];
				String ffn = Util.friendAlbumName(fbUser.uid,fid);
				l.add(dirEntryFromFileName(ffn, fbUser.updatedTime[i], Constants.PriorityMeta));
			}
				
			updateNoti(fbUser.uid, l);
			
		}
	}
	
	//Routine to check for update from friends
	public void checkFriends() {
		while (true) {
			synchronized (sharedData.fbUsers) {
				System.out.println("checkFriends routine " + sharedData.fbUsers.size());
				List<Future<?>> futures = new LinkedList<Future<?>>();
				
				Set<String> keySet = sharedData.fbUsers.keySet();
				
				for (String f:keySet) {
					FacebookUser fbUser=sharedData.fbUsers.get(f);
					QueryUserTask qut = new QueryUserTask(fbUser);
					futures.add(queryUserThreadPool.submit(qut));						
				}
				
				//Wait for all the children threads to complete
				for (Future<?> future:futures) {
					try {
						future.get();
					} catch (InterruptedException e) {
						e.printStackTrace();
					} catch (ExecutionException e) {
						e.printStackTrace();
					}
				}
			}

			try {
				semaphore.tryAcquire(300, TimeUnit.SECONDS);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}			
		}
	}
	
//	 /**
//	 * Add a new user to the monitoring list. 
//	 * Initiate friends by reading the friend list from the server. Initiate updatedTime, reading from the local cached files if available.
//	 */
//	public void initUser(FacebookUser fbUser) {
//		//Read the friend list from the FB server
//		try {
//			String friendRes;
//			friendRes = fbUser.facebook.request("me/friends");
//
//			JSONObject friendJson = FacebookUtil.parseJson(friendRes);
//			JSONArray friends = friendJson.getJSONArray("data");
//
//			int ll = friends.length();
//			
//			System.out.println("Length " + (1+ll));
//			
//			FriendInfo[] fi = new FriendInfo[1+ll];
//			
//			fi[0] = new FriendInfo();
//			fi[0].fid = "me";
//			fi[0].name = "";
//
//			for (int jj=0; jj<ll; jj++) {
//				JSONObject photo = friends.getJSONObject(jj);
//				String fid = photo.getString("id");
//				String name = photo.getString("name");
//				
//				fi[jj+1] = new FriendInfo();
//				fi[jj+1].fid = fid;
//				fi[jj+1].name = name;
//			}
//			
//			Arrays.sort(fi);
//			
//			fbUser.friends = new String[fi.length];
//			fbUser.updatedTime = new String[fbUser.friends.length];
//			
//			for (int jj=0; jj<fbUser.friends.length; jj++) {
//				fbUser.friends[jj] = fi[jj].fid;				
//
//				System.out.println("Friend " + jj + " " + fbUser.friends[jj] + " " + fi[jj].name);
//			}
//		} catch (MalformedURLException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//		} catch (IOException e1) {
//			// TODO Auto-generated catch block
//			e1.printStackTrace();
//		} catch (FacebookError e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		} catch (JSONException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		
//		if (fbUser.friends == null) {
//			fbUser.friends = new String[0];			
//		}
//
//		for (int i=0;i<fbUser.friends.length;i++) {
//			String fid = fbUser.friends[i];
//			String fn = Util.friendAlbumName(fbUser.uid,fid);
//			File f = new File (fn);
//
//			if (i==0) {
//				fbUser.updatedTime[i] = "";
//			} else {
//				fbUser.updatedTime[i] = this.DefaultStratTime;
//			}
//			try {	
//				//System.out.println(f_album[i].getName());
//				if (f.exists()) {
//					FriendAlbum fa = readFriendAlbumFromFile(f);
//					fbUser.updatedTime[i] = fa.updatedTime;
//					System.out.println("File exists");
//					
//				} else {
//					FriendAlbum fa = getFriendInfo(fbUser, fid);
//					writeFriendAlbumToFile(fa, f);
//				}
//				
//				System.out.println("updatedTime[" + i + "]: " + fbUser.updatedTime[i]);
//			} catch (FileNotFoundException e) {
//				e.printStackTrace();
//			} catch (IOException e) {
//				e.printStackTrace();
//			} catch (TException e) {
//				e.printStackTrace();
//			}
//		}
//
//		//Write the friend list to file
//		String fn = Util.friendListName(fbUser.uid);
//		File f = new File(fn);
//		FileOutputStream fos;
//		try {
//			fos = new FileOutputStream(f);
//			ObjectOutputStream oos = new ObjectOutputStream (fos);
//			oos.writeObject(fbUser.friends);
//			oos.close();
//		} catch (FileNotFoundException e) {
//			e.printStackTrace();
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
//
//		String curTime = Util.getCurTimeString();
//		
//		//Add notification for the client
//		ArrayList<DirEntry> l = new ArrayList<DirEntry> ();
//		l.add(this.dirEntryFromFileName(fn, curTime, Para.PRIORITY_META));
//		
//		for (int i=0;i<fbUser.friends.length;i++) {
//			String fid = fbUser.friends[i];
//			String ffn = Util.friendAlbumName(fbUser.uid,fid);
//			l.add(this.dirEntryFromFileName(ffn, fbUser.updatedTime[i], Para.PRIORITY_META));
//		}
//			
//		updateNoti(fbUser.uid, l);
//	}
//	 
//	  
//	  
//	  
//	//Routine to check for update from friends
//		public void checkFriends() {
//			while (true) {
//				synchronized (sharedData.fbUsers) {
//					System.out.println("checkFriends routine " + sharedData.fbUsers.size());
//					for (int f=0;f<sharedData.fbUsers.size();f++) {
//						FacebookUser fbUser=sharedData.fbUsers.get(f);
//
//						if (fbUser.newUser) {
//							initUser(fbUser);
//							fbUser.newUser = false;
//						}
//
//						//The list of files, including album files and photos files of all friends,
//						//that have been updated.
//						List<DirEntry> fnList = new ArrayList<DirEntry> ();
//
//						for (int i=0;i<fbUser.friends.length;i++) {
//							try {
//								System.out.println("UpdateMonitor Routine " + f + "-" + i + " " + fbUser.updatedTime[i]);
//								String fid = fbUser.friends[i];
//
//								String albumRes = fbUser.facebook.request(fid+"/albums");
//								JSONObject albumJson = FacebookUtil.parseJson(albumRes);
//								JSONArray albums = albumJson.getJSONArray("data");
//
//								//The list of albums that have been updated
//								ArrayList<MyAlbum> albumList = new ArrayList<MyAlbum> ();
//
//								//Check the tagged photo of the user, which is under /me/photos
//								if (i==0){
//									MyAlbum mAlbum = new MyAlbum();
//									mAlbum.id = "me";
//									albumList.add(mAlbum);
//								}
//
//								String largetTime = fbUser.updatedTime[i];
//
//								for (int j=0; j<albums.length();j++) {
//									MyAlbum mAlbum = new MyAlbum();
//									JSONObject album = albums.getJSONObject(j);
//									mAlbum.id = album.getString("id");
//									if (album.has("updated_time")) {
//										mAlbum.updated_time = album.getString("updated_time");
//									} else {
//										mAlbum.updated_time = album.getString("created_time");
//									}
//									mAlbum.name = album.getString("name");
//									//Check whether the latest udpate of the album is cached
//									if (mAlbum.updated_time.compareTo(fbUser.updatedTime[i]) > 0) {
//										albumList.add(mAlbum);
//									}
//									if (mAlbum.updated_time.compareTo(largetTime) > 0) {
//										System.out.println("***Largest time updated from " + largetTime + " to " + mAlbum.updated_time);
//										largetTime = mAlbum.updated_time;
//									}
//								}
//
//								System.out.println("AlbumListSize " + albumList.size());
//								if (albumList.size() > 0) {
//									//Some albums have been updated
//									List<PhotoEntry> newPhotoList = new ArrayList<PhotoEntry>();
//									for (int j=0;j<albumList.size();j++) {
//										System.out.println("Album " + j + " " + albumList.get(j).id);
//										try {
//											MyAlbum mAlbum = albumList.get(j);
//											String albumId = mAlbum.id;
//											String photoRes = fbUser.facebook.request(albumId+"/photos");
//											JSONObject photoJson = FacebookUtil.parseJson(photoRes);
//											JSONArray photos = photoJson.getJSONArray("data");
//											
//											for (int jj=0; jj<photos.length(); jj++) {
//												JSONObject photo = photos.getJSONObject(jj);
//												String pid = photo.getString("id");
//												String source = photo.getString("source");
//												String pic = photo.getString("picture");
//
//												System.out.println("Photos " + jj + " " + pid);
//												
//												PhotoEntry pe = new PhotoEntry();
//												pe.updatedTime = photo.getString("updated_time");
//
//												if (photo.has("name")) {
//													pe.name = photo.getString("name");
//												} else {
//													pe.name = "";
//												}
//												if (photo.has("comments")) {
//													JSONArray comments = photo.getJSONObject("comments").getJSONArray("data");
//													for (int tc = 0;tc<comments.length(); tc++) {
//														JSONObject tag = comments.getJSONObject(tc);
//														String ct = tag.getString("created_time");
//														System.out.println("comments " + tc + " " + ct);
//														if (ct.compareTo(pe.updatedTime) > 0) {
//															pe.updatedTime = ct;
//														}
//													}
//													pe.comments = comments.length();
//												} else {
//													pe.comments = 0;
//												}
//
//												if (photo.has("tags")) {
//													JSONArray tags = photo.getJSONObject("tags").getJSONArray("data");
//													for (int tc = 0;tc<tags.length(); tc++) {
//														JSONObject tag = tags.getJSONObject(tc);
//														String ct = tag.getString("created_time");
//														if (ct.compareTo(pe.updatedTime) > 0) {
//															pe.updatedTime = ct;
//														}
//													}
//													pe.tags = tags.length();
//												} else {
//													pe.tags = 0;
//												}
//
//												//Get the likes
//												String likesRes = fbUser.facebook.request(pid+"/likes");
//												JSONObject likesJson = FacebookUtil.parseJson(likesRes);
//												JSONArray likes = likesJson.getJSONArray("data");
//
//												pe.likes = likes.length();
//
//												pe.album_id = albumId;
//												pe.links = photo.getString("link");
//												pe.id = pid;
//												pe.source = source;
//												pe.picture = pic;
//																							
//												System.out.println("P " + pe.id + " " + pe.name + " comments: " + pe.comments + " tags: " + pe.tags + " likes: " + pe.likes + "\n" + pe.links);
//
//												if (pe.updatedTime.compareTo(DefaultStratTime) > 0) {
//													newPhotoList.add(pe);
//												}
//												
//												if (pe.updatedTime.compareTo(fbUser.updatedTime[i]) > 0) {
//													String sourceFn = Util.photoName(pe.id);
//													cachePhoto(source, sourceFn);
//													String thumbFn = Util.photoThumbName(pe.id);
//													cachePhoto(pic, thumbFn);
//
//													fnList.add(dirEntryFromFileName(sourceFn, pe.updatedTime, Para.PRIORITY_NORMAL));
//													fnList.add(dirEntryFromFileName(thumbFn, pe.updatedTime, Para.PRIORITY_THUMB));
//
//													if (pe.updatedTime.compareTo(largetTime) > 0) {
//														System.out.println("***Largest time updated from " + largetTime + " to " + pe.updatedTime);
//														largetTime = pe.updatedTime;												
//													}
//												}
//											}
//										} catch (JSONException e) {
//											e.printStackTrace();
//										} catch (FacebookError e) {
//											e.printStackTrace();
//										} catch (FileNotFoundException e) {
//											e.printStackTrace();
//										} catch (MalformedURLException e) {
//											e.printStackTrace();
//										} catch (IOException e) {
//											e.printStackTrace(); 
//										}
//									}
//
//									fbUser.updatedTime[i] = largetTime;
//									
//									updateFriendAlbum(fbUser.uid, fid, newPhotoList,fbUser.updatedTime[i], albumList);
//									
//									String tmpFAFn =  Util.friendAlbumName(fbUser.uid, fbUser.friends[i]);
//									fnList.add(dirEntryFromFileName(tmpFAFn, fbUser.updatedTime[i], Para.PRIORITY_META));
//								}
//							} catch (JSONException e) {
//								e.printStackTrace();
//							} catch (FacebookError e) {
//								e.printStackTrace();
//							} catch (FileNotFoundException e) {
//								e.printStackTrace();
//							} catch (MalformedURLException e) {
//								e.printStackTrace();
//							} catch (IOException e) {
//								e.printStackTrace();
//							}
//							if (fnList.size() > 0) {
//								updateNoti(fbUser.uid,fnList);
//								fnList.clear();
//							}			
//						}
//					}				
//				}
//
//				synchronized (this) {
//					try {
//						this.wait(60000);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//				}
//			}
//		}
		
	
//
//	public FriendAlbum getFriendInfo(FacebookUser fbUser, String fid) {
//		String response;
//		try {
//			response = fbUser.facebook.request(fid);
//
//			JSONObject json = FacebookUtil.parseJson(response);
//			FriendAlbum fa = new FriendAlbum();
//			fa.friend = new FriendEntry();
//			fa.friend.id = json.getString("id");
//			fa.friend.name = json.getString("name");
//			fa.friend.profilePicId = "";
//			fa.updatedTime = this.DefaultStratTime;
//			fa.photos = new ArrayList<PhotoEntry> ();
//			
//			//Get the profile picture
//			String albumRes = fbUser.facebook.request(fid+"/albums");
//			JSONObject albumJson = FacebookUtil.parseJson(albumRes);
//			JSONArray albums = albumJson.getJSONArray("data");
//
//			ArrayList<MyAlbum> albumList = new ArrayList<MyAlbum> (albums.length());
//			for (int j=0; j<albums.length();j++) {
//				JSONObject album = albums.getJSONObject(j);
//
//				if (album.getString("type").compareTo("profile") == 0) {
//					String photoRes = fbUser.facebook.request(album.getString("id")+"/photos");
//					JSONObject photoJson = FacebookUtil.parseJson(photoRes);
//					JSONArray photos = photoJson.getJSONArray("data");
//					if (photos.length()>0) {
//						JSONObject profile = photos.getJSONObject(0);							
//						fa.friend.profilePicId = profile.getString("id");
//						String updatedTime = profile.getString("updated_time");
//						String fn = Util.photoName(fa.friend.profilePicId);
//						this.cachePhoto(profile.getString("picture"), fn);
//						
//						ArrayList <DirEntry> l = new ArrayList<DirEntry> (1);
//						l.add(dirEntryFromFileName(fn, updatedTime, Para.PRIORITY_THUMB));
//						this.updateNoti(fbUser.uid, l);									
//					}
//				}
//			}
//			return fa;
//		} catch (MalformedURLException e) {
//			e.printStackTrace();
//		} catch (IOException e) {
//			e.printStackTrace();
//		} catch (FacebookError e) {
//			e.printStackTrace();
//		} catch (JSONException e) {
//			e.printStackTrace();
//		}
//
//		return null;
//	}
	
	
	/*
	public void checkAlbum(String aid) {
		String fn_album = Para.CacheRoot + "album_" + aid + "_json";
		File f_album = new File (fn_album);
		String updatedTime = "";

		if (f_album.exists()) {
			try {
				FileInputStream fis = new FileInputStream(f_album);
				String s = FacebookUtil.readString (fis);

				fis.close();

				JSONObject jsonAlbum;

				jsonAlbum = FacebookUtil.parseJson(s);
				updatedTime = jsonAlbum.getString("updated_time");
			} catch (JSONException e) {
				e.printStackTrace();
			} catch (FacebookError e) {
				e.printStackTrace();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		while (true) {
			System.out.println("UpdateMonitor Routine");
			List<File> fnList = new ArrayList<File> ();
			String fn_photos = Para.CacheRoot + "album_" + aid + "_photos";
			File f_photos = new File (fn_photos);
			try {
				String responseAlbum = facebook.request(aid);
				JSONObject jsonCurAlbum = FacebookUtil.parseJson(responseAlbum);
				String curUpdatedTime =  jsonCurAlbum.getString("updated_time");

				if (curUpdatedTime.compareTo(updatedTime) != 0 || !f_album.exists() || !f_photos.exists() ) {
					updatedTime = curUpdatedTime;

					//Pull the Album object on the server
					FileOutputStream fos = new FileOutputStream(f_album);
					BufferedOutputStream out = new BufferedOutputStream(fos);
					out.write(responseAlbum.getBytes());
					out.close();

					fnList.add(f_album);

					//Read the locally cached photo list, and build a map from photo_id to updated_time

					Map<String, String> photoUpdatedTime = new HashMap<String, String>();

					if (f_photos.exists()) {
						FileInputStream fis = new FileInputStream(f_photos);
						String s = FacebookUtil.readString (fis);
						fis.close();

						JSONObject jsonPhotos;
						jsonPhotos = FacebookUtil.parseJson(s);
						JSONArray dataPhotos = jsonPhotos.getJSONArray("data");
						for (int i = 0; i < dataPhotos.length(); i++) {
							JSONObject obj = dataPhotos.getJSONObject(i);
							photoUpdatedTime.put(obj.getString("id"), obj.getString("updated_time"));
						}							
					}

					String responsePhotos = facebook.request(aid + "/photos");
					fos = new FileOutputStream(f_photos);
					out = new BufferedOutputStream(fos);
					out.write(responsePhotos.getBytes());
					out.close();

					fnList.add(f_photos);

					JSONObject jsonPhotos = FacebookUtil.parseJson(responsePhotos);
					JSONArray dataPhotos = jsonPhotos.getJSONArray("data");
					for (int i = 0; i < dataPhotos.length(); i++) {
						JSONObject obj = dataPhotos.getJSONObject(i);
						String id = obj.getString("id");
						String ut = obj.getString("updated_time");

						if (!photoUpdatedTime.containsKey(id) || ut.compareTo(photoUpdatedTime.get(id))!=0) {
							System.out.println("Updated photo " + id + " at " + ut);

							//Prefetch to photo
							String sourcePhoto = obj.getString("source");
							String sourceFn = Para.CacheRoot + id + ".jpg";
							cachePhoto(sourcePhoto, sourceFn);
							String thumbPhoto = obj.getString("picture");
							String thumbFn = Para.CacheRoot + id + "_thumb.jpg";
							cachePhoto(sourcePhoto, thumbFn);
							fnList.add(new File(sourceFn));
							fnList.add(new File(thumbFn));
						}
					}
				} else {
					System.out.println("Latest album");
				}

				if (fnList.size() > 0) {
				updateNoti(uid,fnList);
				}

				Thread.sleep(5000) ;
			} catch (JSONException e) {
				e.printStackTrace();
			} catch (FacebookError e) {
				e.printStackTrace();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	 */
	
	public void updateNoti(String uid, List<DirEntry> fnList) {
		String fn = Util.notiName(uid);

		File f = new File(fn);
		HashMap<String, DirEntry> res = null;

		if (sharedData.userNotiMonitor.get(uid) == null) {
			return;
		}
		
		synchronized (sharedData.userNotiMonitor.get(uid)) {
			if (f.exists()) {
				FileInputStream fis;
				try {
					fis = new FileInputStream(f);
					ObjectInputStream ois = new ObjectInputStream (fis);
					res = (HashMap<String, DirEntry>) ois.readObject();	
					ois.close();
				} catch (FileNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} 
			if (res == null) {
				res = new HashMap<String, DirEntry>();
			}

			System.out.println("before: DirEntry list size " + res.size());
			for (int i = 0; i < fnList.size(); i++) {
				File lf = new File (Para.CacheRoot + fnList.get(i).name);
				System.out.println("fnList " + i + " " + lf.getPath());
				if (lf.exists() && lf.isFile()) {
					DirEntry de = fnList.get(i);
					res.put(de.name, de);
				}
			}

			System.out.println("after: DirEntry list size " + res.size());
			try {
				FileOutputStream fos = new FileOutputStream(f);
				ObjectOutputStream oos = new ObjectOutputStream(fos);
				oos.writeObject(res);

				oos.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public DirEntry dirEntryFromFileName(String fn, String updatedTime, int priority) {
		File f = new File(fn);
		
		DirEntry de = new DirEntry();
		de.attr = new Attr();
		de.name = f.getName();
		//System.out.println("dirEntryFromFileName " + de.name);
		de.attr.size = f.length();
		de.attr.createTime = updatedTime;
		de.attr.modifyTime = updatedTime;
		de.attr.accessTime = updatedTime;
		de.attr.priority = priority;
		
		return de;
	}
	
	public JSONObject FriendAlbumToJson(FriendAlbum fa) {
		try {
			JSONObject j_FriendAlbum = new JSONObject();
			JSONObject j_FriendEntry = new JSONObject();

			j_FriendEntry.put("id", fa.friend.id);
			j_FriendEntry.put("name", fa.friend.name);
			j_FriendEntry.put("profilePicId", fa.friend.profilePicId);

			JSONArray j_Photos = new JSONArray();
			for (int i=0;i<fa.photos.size();i++) {
				PhotoEntry pe = fa.photos.get(i);
				JSONObject j_Photo = new JSONObject();
				j_Photo.put("id", pe.id);
				j_Photo.put("name", pe.name);
				j_Photo.put("updatedTime", pe.updatedTime);
				j_Photo.put("source", pe.source);
				j_Photo.put("picture", pe.picture);
				j_Photos.put(j_Photo);				
			}

			j_FriendAlbum.put("friend", j_FriendEntry);
			j_FriendAlbum.put("updatedTime", fa.updatedTime);
			j_FriendAlbum.put("photos", j_Photos);

			return j_FriendAlbum;
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}
	
	public boolean updateFriendAlbum(String uid, String fid, List<PhotoEntry> photoList, String updatedTime, List<MyAlbum> albumList) {
		if (photoList == null || photoList.size() == 0) {
			return false;
		}
		
		String fn = Util.friendAlbumName(uid, fid);

		System.out.println("updateFriendAlbum " + fn);
		File f = new File(fn);
		FriendAlbum res = null;

		if (f.exists()) {
			try {
				res = readFriendAlbumFromFile(f);
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (TException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} 
		
		if (res == null) {
			System.out.println("null FriendAlbum read from file!");
			return false;
		}
		
		res.updatedTime = updatedTime;

		System.out.println("before: FriendAlbum list size " + res.photos.size());

		//Dump the photos from the albums that are not updated
		Map<String, Integer> changedAlbum = new HashMap<String, Integer>();
		Map<String, Integer> imageMap = new HashMap<String, Integer>();
		for (int i=0;i<albumList.size();i++) {
			changedAlbum.put(albumList.get(i).id, 1);
		}
		
		ArrayList<PhotoEntry> newList = new ArrayList<PhotoEntry> ();
		
		for (int i=0;i<res.photos.size();i++) {
			PhotoEntry pe = res.photos.get(i);
			//No need to check whether the album is changed or not 
			//if (changedAlbum.get(pe.album_id) == null && imageMap.get(pe.id) == null) {
			if (imageMap.get(pe.id) == null) {
				newList.add(pe);
				imageMap.put(pe.id, 1);
			}
		}
		
		for (int i=0;i<photoList.size();i++) {
			PhotoEntry pe = photoList.get(i);
			if (imageMap.get(pe.id) == null) {
				newList.add(pe);
				imageMap.put(pe.id, 1);
			}
		}
		
		Collections.sort(newList, new PhotoEntrySortByTime());
		
		res.photos = newList;
		res.totalPhotos = res.photos.size();

		System.out.println("after: FriendAlbum list size " + res.photos.size());
		for (int j=0;j<res.photos.size();j++) {
			System.out.println(""+j + ":" + res.photos.get(j).id + " " + res.photos.get(j).album_id + " " + res.photos.get(j).updatedTime);
		}

		try {
			writeFriendAlbumToFile(res, f);			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TException e) {
			e.printStackTrace();
		}
		
		return true;
	}

	public class PhotoEntrySortByTime implements Comparator<PhotoEntry>{
		@Override
		public int compare(PhotoEntry o1, PhotoEntry o2) {
			return o1.updatedTime.compareTo(o2.updatedTime);
		}
	}

	/**
	 * Fetch a photo from the server
	 * 
	 * @param source the source link of the photo data
	 * @param localPath the path to store the fetched photo
	 */
	public void cachePhoto(String source, String localPath) {
		//TODO: asynchronous
		try {
			File file = new File (localPath);

			if (!file.exists()) {
				InputStream is = null;

				is = FacebookUtil.openUrlBin(source, "GET",new Bundle() {});

				int retry = 3;
				
				while (is == null && retry > 0) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					is = FacebookUtil.openUrlBin(source, "GET",new Bundle() {});
					retry--;
				}

				System.out.println("cachePhoto to " + localPath); 	

				FileOutputStream fos = new FileOutputStream(localPath);
				BufferedOutputStream bos = new BufferedOutputStream(fos);
				
				FacebookUtil.readBin(is, bos);
				bos.close();
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		checkFriends();
	}

	//Read FriendAlbum object from file
	public FriendAlbum readFriendAlbumFromFile(File f) throws TException, IOException {
		FileInputStream fis = new FileInputStream(f);
		TIOStreamTransport tt = new TIOStreamTransport(fis);
		TBinaryProtocol tpl = new TBinaryProtocol(tt);

		FriendAlbum fa = new FriendAlbum();
		fa.read(tpl);
		tt.close();	
		
		fis.close();
		
		return fa;
	}

	//Write FriendAlbum object to file	
	public void writeFriendAlbumToFile(FriendAlbum fa, File f) throws TException, IOException {
		System.out.println("WriteFAtoFile " + f.getName() + " " + (fa.friend==null));
		FileOutputStream fos = new FileOutputStream(f);
		TIOStreamTransport tt = new TIOStreamTransport(fos);
		TBinaryProtocol tpl = new TBinaryProtocol(tt);

		fa.write(tpl);

		tt.close();
		
		fos.close();
	}

	/*
	public FriendAlbum FriendAlbumFromJson(JSONObject json) {
		FriendAlbum fa = new FriendAlbum();
		try {
			JSONObject friend = json.getJSONObject("friend");
			fa.friend = new FriendEntry();
			fa.friend.id = friend.getString("id");
			fa.friend.name = friend.getString("name");
			fa.friend.profilePicId = friend.getString("profilePicId");
			fa.updatedTime = json.getString("updatedTime");
			JSONArray photos = json.getJSONArray("photos");
			fa.photos = new ArrayList<PhotoEntry> ();
			for (int i=0;i<photos.length();i++) {
				JSONObject obj = photos.getJSONObject(i);
				PhotoEntry pe = new PhotoEntry();
				pe.id = obj.getString("id");
				pe.picture = obj.getString("picture");
				pe.source = obj.getString("source");
				pe.updatedTime = obj.getString("updatedTime");
				fa.photos.add(pe);		
			}
			return fa;
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}
	 */
}
